/**
 * @依赖 npm i mysql
 * @功能点
 *     - 数据库连接设置为 连接池 并 支持心跳包
 *     - 对外暴露 Mysql57类 提供以下方法
 *         1. sqlExec                普通执行sql语句并关闭连接
 *         2. sqlExecInTransaction   在事务环境下执行sql语句不关闭连接
 *         3. startTransaction       开始事务
 *         4. commitTransaction      提交事务
 *         5. rollbackTransaction    回滚事务
 * @注意 本驱动只对 mysql库 进行再封装 读取数据库连接信息的操作应开发者自行解决
 */

const mysql = require('mysql');

class Mysql57 {
    constructor(config) {
        // 数据库连接对象
        this.db = null;
        // 数据库连接信息
        this.host = config.host;
        this.port = config.port;
        this.user = config.user;
        this.pswd = config.pswd;
        this.database = config.database;
        // 数据库心跳包间隔时间 默认3分钟
        this.heartbeat = config.heartbeat ?? 3 * 60 * 1000;
    }

    /*-------------------------------- 初始化 --------------------------------*/
    // 数据库初始化的方法
    init() {
        // 限制 mysql 只初始化一次
        if (this.db) return ;
        return new Promise((resolve, reject) => {
            // 输出连接信息
            console.log('正在连接 mysql 数据库 ...', this.host, this.port);
            // 连接数据库
            this.db = mysql.createPool({
                host: this.host,
                port: this.port,
                user: this.user,
                password: this.pswd,
                database: this.database,
                idleTimeoutMinutes: 60,
                acquireTimeout: 60000,
                waitForConnections: true,
                charset: 'utf8mb4',
                collation: 'utf8mb4_unicode_ci'
            });
            // 检测数据库是否连接成功
            this.db.getConnection((err, connection) => {
                if (err) {
                    console.error(`mysql数据库连接失败 ! ! ! !`);
                    console.error(` -> host: ${this.host}`);
                    console.error(` -> port: ${this.port}`);
                    console.error(` -> user: ${this.user}`);
                    console.error(` -> password: ${this.pswd}`);
                    console.error(` -> database: ${this.database}`);
                    reject("mysql数据库连接失败 ! ! ! !");
                    process.exit(1);
                } else {
                    console.log('mysql 数据库连接成功！  数据库进程id为: ' + connection.threadId);
                    // 释放连接
                    connection.release();
                    // 开启定时器发送心跳包，检查数据库连接状态
                    this.startTimerCheckConnection();
                    // 返回连接成功的状态
                    resolve("mysql57 连接成功 ! ! ! !");
                }
            });
        });
    }

    /*-------------------------------- 心跳 --------------------------------*/
    startTimerCheckConnection() {
        setInterval(() => { this.checkConnection() }, this.heartbeat);
    }
    // 心跳包机制：定期检查连接状态
    checkConnection() {
        this.db.getConnection((err, connection) => {
            if (err) {
                let t = new Date().toLocaleString();
                console.error(`[ INFO ] ${ t } 无法获取数据库连接:`, err);
                return;
            }
            // 使用 ping 方法检查连接状态
            connection.ping((err) => {
                // 释放连接回连接池
                connection.release();
                let t = new Date().toLocaleString();
                if (err) {
                    console.error(`[ INFO ] ${ t } 数据库连接已断开:`, err);
                } else {
                    console.log(`[ INFO ] ${ t } 数据库连接正常`);
                }
            });
        });
    }

    // 释放连接池中单个连接的方法
    releaseConnection(connection) {
        // 连接判空
        if (!connection) {
            throw new Error('connection is null');
        }
        // 尝试释放连接，报错表示重复释放，直接返回即可
        try {
            connection.release();
        } catch (err) {
            return ;
        }
    }

    /*-------------------------------- 基本查询 --------------------------------*/
    // 单步执行sql的方法
    sqlExec(sql, data) {
        return new Promise((resolve, reject) => {
            this.db.getConnection((err, connection) => {
                if (err) {
                    // 连不上数据库
                    this.releaseConnection(connection);
                    reject(err);
                    return ;
                }
                connection.query(sql, data, (err, result) => {
                    // 释放数据库连接
                    this.releaseConnection(connection);
                    if (err) {
                        reject(err);
                        return ;
                    }
                    resolve(result);
                });
            });
        });
    }

    /*-------------------------------- 事务相关 --------------------------------*/
    /**
     * 开启事务的方法
     * 返回一个 Promise 对象，用于在事务开始后返回连接
     */
    startTransaction() {
        return new Promise((resolve, reject) => {
            this.db.getConnection((err, connection) => {
                if (err) {
                    // 连不上数据库
                    reject({ success: false, err});
                    return;
                }
                connection.beginTransaction((err) => {
                    if (err) {
                        // 开启事务失败 释放数据库连接
                        this.releaseConnection(connection);
                        reject({ success: false, err});
                        return;
                    }
                    // 事务开启成功，返回连接以便后续操作
                    resolve({ success: true, connection, isRollback: false, isClose: false, isCommit: false });
                });
            });
        });
    }
    
    // 事务环境下的单步执行的方法
    sqlExecInTransaction(connection, sql, data) {
        connection = connection.connection;
        return new Promise((resolve, reject) => {
            connection.query(sql, data, function (err, result) {
                if (err) {
                    reject(err);
                    return ;
                }
                resolve(result);
            });
        });
    }

    /*
     * 提交事务的方法
     */
    async commitTransaction(connection) {
        let connObj = connection;
        connection = connection.connection;
        if (connObj.isCommit || connObj.isClose || connObj.isRollback) return ;
        return new Promise((resolve, reject) => {
            connection.commit((err) => {
                if (err) {
                    // 提交事务失败
                    connection.rollback(() => {
                        connObj.isCommit = true;
                        connObj.isClose = true;
                        this.releaseConnection(connection);
                        reject(err);
                    });
                    return;
                }
                // 提交事务成功，释放连接
                connObj.isCommit = true;
                connObj.isClose = true;
                this.releaseConnection(connection);
                resolve();
            })
        })
    }

    /*
     * 回滚事务的方法
     */
    async rollbackTransaction(connection) {
        let connObj = connection;
        connection = connection.connection;
        if (connObj.isCommit || connObj.isClose || connObj.isRollback) return ;
        return new Promise((resolve, reject) => {
            connection.rollback((err) => {
                if (err) {
                    // 回滚事务失败
                    connObj.isCommit = true;
                    connObj.isClose = true;
                    this.releaseConnection(connection);
                    reject(err);
                    return;
                }
                // 回滚事务成功，释放连接
                connObj.isRollback = true;
                connObj.isClose = true;
                this.releaseConnection(connection);
                resolve();
            });
        });
    }
}

module.exports = Mysql57;
